﻿/* Copyright (c) 2009, 2010, John Cronin
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the copyright holder nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

using System;
using System.Collections.Generic;
using System.Text;

namespace tysila
{
    partial class Assembler
    {
#if MT
        public void AssembleType(object ttcr) { AssembleType(ttcr as TTCRequest); }
        public void AssembleType(TTCRequest ttcr) { AssembleType(ttcr.ttc, ttcr.of); }
#endif
        public void AssembleType(Assembler.TypeToCompile ttc, IOutputFile of)
        {
            if (of == null)
            {
#if MT
                rtc--;
#endif
                return;
            }

            Layout l = new Layout(ttc, this);

            lock (of)
            {
                /* Write the static fields */
                if (l.StaticFields.Count > 0)
                {
                    of.AddDataSymbol(of.GetData().Count, l.StaticObjectMangledName);
                    for (int i = 0; i < l.StaticClassSize; i++)
                        of.GetData().Add(0);
                }

                /* Write the vtables */
                int vtable_start = of.GetData().Count;
                of.AddDataSymbol(vtable_start, l.VTableMangledName);
                byte[] vtable = new byte[l.VTableSize];
                foreach (Layout.ImplementedInterface.ImplementedInterfaceMember iim in l.Members)
                {
                    switch (iim.Type)
                    {
                        case Layout.Interface.InterfaceMember.InterfaceMemberType.TypeInfoPointer:
                            of.AddDataRelocation(vtable_start + iim.OverallOffset, l.TypeInfoMangledName, DataToDataRelocType(), 0);
                            Requestor.RequestTypeInfo(ttc);
                            break;
                        case Layout.Interface.InterfaceMember.InterfaceMemberType.Method:
                            of.AddDataRelocation(vtable_start + iim.OverallOffset, iim.ImplementedMangledName, DataToDataRelocType(), 0);
                            SetByteArray(vtable, iim.OverallOffset + GetSizeOfPointer(), IntPtrByteArray(iim.ThisAdjust));
                            if (iim.ImplementedMangledName != Assembler.PureVirtualSig)
                                Requestor.RequestMethod(iim.Implementation);
                            break;
                        default:
                            throw new NotImplementedException();
                    }
                }
                foreach (byte b in vtable)
                    of.GetData().Add(b);
            }
#if MT
            rtc--;
#endif
        }

        private void WriteZeroIntPtr(IOutputFile of)
        {
            int count = this.GetSizeOf(new Signature.Param(BaseType_Type.I));
            for (int i = 0; i < count; i++)
                of.GetData().Add(0);
        }

        private void WriteZeroInt32(IOutputFile of)
        {
            int count = this.GetSizeOf(new Signature.Param(BaseType_Type.I4));
            for (int i = 0; i < count; i++)
                of.GetData().Add(0);
        }

#if MT
        public void AssembleTypeInfo(object ttcr) { AssembleTypeInfo(ttcr as TTCRequest); }
        public void AssembleTypeInfo(Assembler.TTCRequest ttcr) { AssembleTypeInfo(ttcr.ttc, ttcr.of); }
#endif
        public void AssembleTypeInfo(Assembler.TypeToCompile ttc, IOutputFile of)
        {
            // Layout is
            //  intptr offset_to_signature
            //  intptr offset_to_typedef_table
            //  intptr offset_to_casttable

            // signature:
            //  int32 length
            //  byte  b1
            //  byte  b2
            //  ..

            //  typedef_table:
            //   int32 length
            //   intptr typename1
            //   intptr metadataname1
            //   int32  rowno1
            //   intptr typename2
            //   intptr metadataname2
            //   int32  rowno2
            //   ..


            // casttable:
            //  int32 length
            //  intptr typeinfo1
            //  intptr this_adjust1
            //  intptr typeinfo2
            //  intptr this_adjust2

            if (of == null)
            {
#if MT
                rtc--;
#endif
                return;
            }

            Layout l = new Layout(ttc, this);

            lock (of)
            {
                IList<byte> data_sect = of.GetData();
                int ti_top = data_sect.Count;

                of.AddDataSymbol(ti_top, l.TypeInfoMangledName);

                int sig_offset_offset = data_sect.Count;
                WriteZeroIntPtr(of);

                int typedef_table_offset_offset = data_sect.Count;
                WriteZeroIntPtr(of);

                int casttable_offset_offset = data_sect.Count;
                WriteZeroIntPtr(of);

                // Write signature
                int signature_top = data_sect.Count;
                WriteZeroInt32(of);
                Dictionary<Metadata.TypeDefRow, byte> typedef_table = new Dictionary<Metadata.TypeDefRow, byte>();
                byte next_typedef_id = 0;
                write_ti_signature(data_sect, ttc, ttc.tsig.Type, typedef_table, ref next_typedef_id);
                SetByteArray(data_sect, signature_top, (int)(data_sect.Count - signature_top - GetSizeOf(new Signature.Param(BaseType_Type.I4))));

                // Write typedef table
                int typedef_table_top = data_sect.Count;
                byte[] typedef_count = ToByteArray((int)next_typedef_id);
                foreach (byte b in typedef_count)
                    data_sect.Add(b);
                foreach (KeyValuePair<Metadata.TypeDefRow, byte> typedef_table_item in typedef_table)
                {
                    var typename_str = ttc.type.m.StringTable.GetStringAddress(typedef_table_item.Key.TypeFullName, this);
                    var metadata_str = ttc.type.m.StringTable.GetStringAddress(typedef_table_item.Key.m.ModuleName, this);
                    int rowno = typedef_table_item.Key.RowNumber;

                    of.AddDataRelocation(data_sect.Count, typename_str.label, DataToDataRelocType(), 0);
                    WriteZeroIntPtr(of);
                    of.AddDataRelocation(data_sect.Count, metadata_str.label, DataToDataRelocType(), 0);
                    WriteZeroIntPtr(of);
                    byte[] rowno_arr = ToByteArray(rowno);
                    foreach (byte b in rowno_arr)
                        data_sect.Add(b);
                }

                // Write cast table
                int casttable_top = data_sect.Count;

                byte[] ct_length = ToByteArray(Convert.ToInt32(l.Interfaces.Count));
                foreach (byte b in ct_length)
                    data_sect.Add(b);

                foreach (Layout.ImplementedInterface ii in l.Interfaces)
                {
                    Layout lii = new Layout(ii.Interface.InterfaceType, this);
                    of.AddDataRelocation(of.GetData().Count, lii.TypeInfoMangledName, DataToDataRelocType(), 0);
                    WriteZeroIntPtr(of);

                    byte[] ct_adjust = IntPtrByteArray(ii.OffsetToTop);
                    foreach (byte b in ct_adjust)
                        data_sect.Add(b);

                    Requestor.RequestTypeInfo(ii.Interface.InterfaceType);
                }

                // Go back to the top to fill in the offsets to various parts of the table
                SetByteArray(data_sect, sig_offset_offset, ToByteArray(ConvertToI(signature_top - ti_top)));
                SetByteArray(data_sect, typedef_table_offset_offset, ToByteArray(ConvertToI(typedef_table_top - ti_top)));
                SetByteArray(data_sect, casttable_offset_offset, ToByteArray(ConvertToI(casttable_top - ti_top)));
            }

#if MT
            rtc--;
#endif
        }

        private void write_ti_signature(IList<byte> sig, TypeToCompile containing_type, Signature.BaseOrComplexType cur_bct, Dictionary<Metadata.TypeDefRow, byte> typedef_table, ref byte next_typedef_id)
        {
            if (cur_bct is Signature.BaseType)
                sig.Add((byte)((Signature.BaseType)cur_bct).Type);
            else if (cur_bct is Signature.ComplexType)
            {
                Metadata.TypeDefRow tdr = Metadata.GetTypeDef(((Signature.ComplexType)cur_bct).Type, this);
                if (tdr.IsValueType(this))
                    sig.Add((byte)BaseType_Type.ValueType);
                else
                    sig.Add((byte)BaseType_Type.Class);
                if (!typedef_table.ContainsKey(tdr))
                    typedef_table.Add(tdr, next_typedef_id++);
                sig.Add(typedef_table[tdr]);
            }
            else if (cur_bct is Signature.BoxedType)
            {
                sig.Add((byte)BaseType_Type.Boxed);
                write_ti_signature(sig, containing_type, ((Signature.BoxedType)cur_bct).Type, typedef_table, ref next_typedef_id);
            }
            else if (cur_bct is Signature.GenericType)
            {
                sig.Add((byte)BaseType_Type.GenericInst);
                write_ti_signature(sig, containing_type, ((Signature.GenericType)cur_bct).GenType, typedef_table, ref next_typedef_id);
                byte[] comp_argcount = Metadata.CompressInteger(Convert.ToUInt32(((Signature.GenericType)cur_bct).GenParams.Count));
                foreach (byte b in comp_argcount)
                    sig.Add(b);
                foreach (Signature.BaseOrComplexType gp in ((Signature.GenericType)cur_bct).GenParams)
                    write_ti_signature(sig, containing_type, gp, typedef_table, ref next_typedef_id);
            }
            else if (cur_bct is Signature.ZeroBasedArray)
            {
                sig.Add((byte)BaseType_Type.SzArray);
                write_ti_signature(sig, containing_type, ((Signature.ZeroBasedArray)cur_bct).ElemType, typedef_table, ref next_typedef_id);
            }
            else
                throw new NotImplementedException(Signature.GetString(cur_bct, this));
        }
    }
}
